﻿namespace NJQuery {
	using System;
	using System.ComponentModel;
	using System.Globalization;

	/// <summary>
	/// Defines an an absolute or aligned position.
	/// </summary>
	[TypeConverter(typeof(PositionConverter))]
	public struct Position : IEquatable<Position>, IComplexProperty {


		#region Static Readonly Fields: Standard values

		/// <summary>
		/// Specifies the top and left positions are to be calculated to center the item horizontally and vertically.
		/// </summary>
		public static readonly Position Center = new Position();

		/// <summary>
		/// Specifies the top and left positions are to be calculated to center the item horizontally align to the top.
		/// </summary>
		public static readonly Position Top = new Position(HorizontalPosition.Center, VerticalPosition.Top);

		/// <summary>
		/// Specifies the top and left positions are to be calculated to center the item vertically and align to the right.
		/// </summary>
		public static readonly Position Right = new Position(HorizontalPosition.Right, VerticalPosition.Middle);

		/// <summary>
		/// Specifies the top and left positions are to be calculated to center the item horizontally and align to the bottom.
		/// </summary>
		public static readonly Position Bottom = new Position(HorizontalPosition.Center, VerticalPosition.Bottom);

		/// <summary>
		/// Specifies the top and left positions are to be calculated to center the item vertically and align to the left.
		/// </summary>
		public static readonly Position Left = new Position(HorizontalPosition.Left, VerticalPosition.Middle);

		/// <summary>
		/// Specifies the top and left positions are to be calculated to align the item to the top-right corner.
		/// </summary>
		public static readonly Position TopRight = new Position(HorizontalPosition.Right, VerticalPosition.Top);

		/// <summary>
		/// Specifies the top and left positions are to be calculated to align the item to the bottom-right corner.
		/// </summary>
		public static readonly Position BottomRight = new Position(HorizontalPosition.Right, VerticalPosition.Bottom);

		/// <summary>
		/// Specifies the top and left positions are to be calculated to align the item to the bottom-left corner.
		/// </summary>
		public static readonly Position BottomLeft = new Position(HorizontalPosition.Left, VerticalPosition.Bottom);

		/// <summary>
		/// Specifies the top and left positions are to be calculated to align the item to the top-left corner.
		/// </summary>
		public static readonly Position TopLeft = new Position(HorizontalPosition.Left, VerticalPosition.Top);

		/// <summary>
		/// Specifies the top and left positions are to be set to the origin.
		/// </summary>
		public static readonly Position Origin = new Position(0, 0);

		#endregion


		#region Static Methods

		/// <summary>
		/// Converts a string representation to its <see cref="Position"/> equivalent.
		/// </summary>
		/// <param name="text">A <see cref="String"/> containing the value to convert.</param>
		/// <param name="result">The parsed value when the conversion succeeds, or a default instance otherwise.</param>
		/// <returns>Indicates whether the conversion succeeded.</returns>
		public static bool TryParse(string text, out Position result) {

			if(string.IsNullOrEmpty(text)) {
				result = Center;
				return false;
			}

			//Handle shorthand
			string trimmedText = text.Trim();
			switch(trimmedText) {
				case "Center": result = Center; return true;
				case "Top": result = Top; return true;
				case "Right": result = Right; return true;
				case "Bottom": result = Bottom; return true;
				case "Left": result = Left; return true;
			}

			string[] splitText = trimmedText.Split(',');
			if(splitText.Length != 2) {
				result = Center;
				return false;
			}

			HorizontalPosition horizontal;
			if(!HorizontalPosition.TryParse(splitText[0].Trim(), out horizontal)) {
				result = Center;
				return false;
			}

			VerticalPosition vertical;
			if(!VerticalPosition.TryParse(splitText[1].Trim(), out vertical)) {
				result = Center;
				return false;
			}

			result = new Position(horizontal, vertical);
			return true;
		}

		#endregion


		#region Fields

		private readonly HorizontalPosition horizontal;
		private readonly VerticalPosition vertical;

		#endregion


		#region Constructors

		/// <summary>
		/// Initializes a new <see cref="Position"/> value.
		/// </summary>
		/// <param name="horizontal">The horizontal position.</param>
		/// <param name="vertical">The vertical position.</param>
		public Position(HorizontalPosition horizontal, VerticalPosition vertical) {
			this.horizontal = horizontal;
			this.vertical = vertical;
		}

		/// <summary>
		/// Initializes a new <see cref="Position"/> value using an absolute position.
		/// </summary>
		/// <param name="leftPosition">The left position (in pixels).</param>
		/// <param name="topPosition">The top position (in pixels).</param>
		public Position(int leftPosition, int topPosition) : this(new HorizontalPosition(leftPosition), new VerticalPosition(topPosition)) { }

		/// <summary>
		/// Initializes a new <see cref="Position"/> value using an absolute horizontal position and aligned vertical position.
		/// </summary>
		/// <param name="leftPosition">The left position (in pixels).</param>
		/// <param name="verticalAlign">The vertical alignment used to calculate a top position.</param>
		/// <exception cref="ArgumentException">Vertical align value must be defined.</exception>
		public Position(int leftPosition, VerticalAlign verticalAlign) : this(new HorizontalPosition(leftPosition), new VerticalPosition(verticalAlign)) { }

		/// <summary>
		/// Initializes a new <see cref="Position"/> value using an absolute vertical position and aligned horizontal position.
		/// </summary>
		/// <param name="horizontalAlign">The horizontal alignment use to calculate a left position.</param>
		/// <param name="topPosition">The top position (in pixels).</param>
		/// <exception cref="ArgumentException">Horizontal align value must be defined.</exception>
		public Position(HorizontalAlign horizontalAlign, int topPosition) : this(new HorizontalPosition(horizontalAlign), new VerticalPosition(topPosition)) { }

		/// <summary>
		/// Initializes a new <see cref="Position"/> value using a calculated position.
		/// </summary>
		/// <param name="horizontalAlign">The horizontal alignment use to calculate a left position.</param>
		/// <param name="verticalAlign">The vertical alignment used to calculate a top position.</param>
		/// <exception cref="ArgumentException">Align values must be defined.</exception>
		public Position(HorizontalAlign horizontalAlign, VerticalAlign verticalAlign) : this(new HorizontalPosition(horizontalAlign), new VerticalPosition(verticalAlign)) { }

		#endregion


		#region Properties

		/// <summary>
		/// Gets the horizontal position.
		/// </summary>
		public HorizontalPosition Horizontal {
			get { return horizontal; }
		}

		/// <summary>
		/// Gets the vertical position.
		/// </summary>
		public VerticalPosition Vertical {
			get { return vertical; }
		}

		#endregion


		#region Methods: Object management

		/// <summary>
		/// Determines whether or not the specified object is equal to the current <see cref="Position"/> value.
		/// </summary>
		/// <param name="obj">The <see cref="Object"/> to compare.</param>
		/// <returns>Whether or not the provided object is equal.</returns>
		public override bool Equals(object obj) {
			if(obj is Position) return this == (Position)obj;
			return false;
		}

		/// <summary>
		/// Determines whether or not the specified Position is equal to the current <see cref="Position"/> value.
		/// </summary>
		/// <param name="other">The <see cref="Position"/> to compare.</param>
		/// <returns>Whether or not the provided value is equal.</returns>
		public bool Equals(Position other) {
			return this == other;
		}

		/// <summary>
		/// Serves as a hash function for <see cref="Position"/>.
		/// </summary>
		/// <returns>The hash value associated with the contained data.</returns>
		public override int GetHashCode() {
			//TODO: Get TheMuuj on this guy
			return horizontal.GetHashCode() & vertical.GetHashCode();
		}

		/// <summary>
		/// Returns a <see cref="String"/> that represents the current <see cref="Position"/> value.
		/// </summary>
		/// <returns>The string representation.</returns>
		public override string ToString() {

			//Handle Shorthand
			if(this == Center) return HorizontalAlign.Center.ToString();
			if(this == Top) return VerticalAlign.Top.ToString();
			if(this == Right) return HorizontalAlign.Right.ToString();
			if(this == Bottom) return VerticalAlign.Bottom.ToString();
			if(this == Left) return HorizontalAlign.Left.ToString();

			return string.Format(CultureInfo.InvariantCulture, "{0},{1}", horizontal, vertical);
		}

		/// <summary>
		/// Returns a literal JavaScript value used to represent the server value on the client.
		/// </summary>
		/// <returns>The client script value.</returns>
		public string ToClientSource() {

			//Handle Shorthand
			if(this == Center) return HorizontalAlign.Center.ToClientSource();
			if(this == Top) return VerticalAlign.Top.ToClientSource();
			if(this == Right) return HorizontalAlign.Right.ToClientSource();
			if(this == Bottom) return VerticalAlign.Bottom.ToClientSource();
			if(this == Left) return HorizontalAlign.Left.ToClientSource();

			return string.Format(CultureInfo.InvariantCulture, "[{0},{1}]", horizontal.ToClientSource(), vertical.ToClientSource());
		}

		#endregion


		#region Operators

		/// <summary>
		/// Compares the equality of two <see cref="Position"/> values.
		/// </summary>
		/// <param name="value0">The first value.</param>
		/// <param name="value1">The second value.</param>
		/// <returns>Whether or not the two values are equal.</returns>
		public static bool operator ==(Position value0, Position value1) {
			return value0.horizontal == value1.horizontal && value0.vertical == value1.vertical;
		}

		/// <summary>
		/// Compares the inequality of two <see cref="Position"/> values.
		/// </summary>
		/// <param name="value0">The first value.</param>
		/// <param name="value1">The second value.</param>
		/// <returns>Whether or not the two values are distinct.</returns>
		public static bool operator !=(Position value0, Position value1) {
			return value0.horizontal != value1.horizontal || value0.vertical != value1.vertical;
		}

		#endregion


	}

}